package com.stickycoding.Rokon;

import javax.microedition.khronos.opengles.GL10;

import com.stickycoding.Rokon.Handlers.CollisionHandler;
import com.stickycoding.Rokon.Handlers.DynamicsHandler;

/**
 * @author Richard
 * Handles all positions, rotations and movement
 * Extended by Sprite, Emitter, Particle 
 * Created to bring together all dynamic methods for different types of object
 */
public class DynamicObject extends Sprite{

	public static final int MAX_COLLIDERS = 0;
	
	private int i, j, k, r;
	
	private Sprite[] _collidersArr = new Sprite[MAX_COLLIDERS];
	private int _colliderCount = 0;
	
	private CollisionHandler _collisionHandler;

	public int intVar1, intVar2, intVar3;
	
	private float _terminalVelocityX, _terminalVelocityY;
	private float _velocityX, _velocityY;
	private float _accelerationX, _accelerationY;
	private boolean _stopAtTerminalVelocity, _triggeredReachTerminalVelocityX, _triggeredReachTerminalVelocityY;
	
	private DynamicsHandler _dynamicsHandler;
	
	private long _timeDiff;
	private float _timeDiffModifier;

	private float _gravityX;
	private float _gravityY;
	
	
	public DynamicObject(float x, float y, float width, float height,
			Texture texture) {
		super(x, y, width, height, texture);
		// TODO Auto-generated constructor stub
	}

	public DynamicObject(float x, float y, float width, float height) {
		super(x, y, width, height);
		// TODO Auto-generated constructor stub
		resetDynamics();
		setLastUpdate();
	}
	public void resetModifiers() {
		resetDynamics();
		super.resetModifiers();
	}

	public DynamicObject(float x, float y, Texture texture) {
		super(x, y, texture);
		// TODO Auto-generated constructor stub
	}
	
	/**
	 * Stops the DynamicObjet, setting acceleration and velocities to zero
	 */
	public void stop() {
		resetDynamics();
	}
	
	public void resetDynamics() {
		_terminalVelocityX = 0;
		_terminalVelocityY = 0;
		_stopAtTerminalVelocity = false;
		_velocityX = 0;
		_velocityY = 0;
		_accelerationX = 0;
		_accelerationY = 0;
	}
	
	/**
	 * Sets the time of the last update to an arbitrary value
	 * @param time
	 */
	public void setLastUpdate(long time) {
		_lastUpdate = time;
	}
	
	public void setGravity(float gravityX, float gravityY) {
		_gravityX = gravityX;
		_gravityY = gravityY;
	}

	
	/**
	 * Updates movement
	 */
	public void updateMovement() {
		super.updateMovement();
		_timeDiff = (Rokon.getTime() - _lastUpdate);
		_timeDiffModifier = 0.1f;
		System.out.println(_timeDiff);
		if(_gravityX != 0 || _gravityY != 0 || _accelerationX != 0 
				|| _accelerationY != 0 || _velocityX != 0 || _velocityY != 0) {
			if(_accelerationX != 0 || _accelerationY != 0 || _gravityX != 0 || _gravityY != 0) {
				_velocityX += (_accelerationX + _gravityX) * _timeDiffModifier;
				_velocityY += (_accelerationY + _gravityY) * _timeDiffModifier;
				if(_stopAtTerminalVelocity) {
					if(!_triggeredReachTerminalVelocityX) {
						if((_accelerationX > 0.0f && _velocityX >= _terminalVelocityX)
						|| (_accelerationX < 0.0f && _velocityX <= _terminalVelocityX)) {
							if(_dynamicsHandler != null)
								_dynamicsHandler.reachedTerminalVelocityX();
							_accelerationX = 0;
							_velocityX = _terminalVelocityX;
							_triggeredReachTerminalVelocityX = true;
						}
					}
					if(!_triggeredReachTerminalVelocityY) {
						if((_accelerationY > 0.0f && _velocityY >= _terminalVelocityY)
						|| (_accelerationY < 0.0f && _velocityY <= _terminalVelocityY)) {
							if(_dynamicsHandler != null)
								_dynamicsHandler.reachedTerminalVelocityY();
							_accelerationY = 0;
							_velocityY = _terminalVelocityY;
							_triggeredReachTerminalVelocityY = true;
						}
					}
				}
			}
			
			_x += _velocityX * _timeDiffModifier;
			_y += _velocityY * _timeDiffModifier;
			System.out.println("v " + _velocityX + " " + _velocityY + " " + _timeDiffModifier);
			updateVertexBuffer();
		}
		setLastUpdate();
	}

	/**
	 * @param dynamicsHandler sets a handler for the dynamics, this can track acceleration
	 */
	public void setDynamicsHandler(DynamicsHandler dynamicsHandler) {
		_dynamicsHandler = dynamicsHandler;
	}
	
	/**
	 * Removes the DynamicsHandler from the Sprite
	 */
	public void resetDynamicsHandler() {
		_dynamicsHandler = null;
	}
	
	/**
	 * Accelerates a Sprite, note that this is relative to current Acceleration.
	 * @param accelerationX acceleration in X direction, pixels per second
	 * @param accelerationY acceleration in Y direction, pixels per second
	 * @param terminalVelocityX specifies a highest possible velocity in X direction, this will trigger reachedTerminalVelocityX
	 * @param terminalVelocityY specifies a highest possible velocity in Y direction, this will trigger reachedTerminalVelocityY
	 */
	public void accelerate(float accelerationX, float accelerationY, float terminalVelocityX, float terminalVelocityY) {
		_stopAtTerminalVelocity = true;
		_terminalVelocityX = terminalVelocityX;
		_terminalVelocityY = terminalVelocityY;
		_accelerationX += accelerationX;
		_accelerationY += accelerationY;
		_triggeredReachTerminalVelocityX = false;
		_triggeredReachTerminalVelocityY = false;
		setLastUpdate();
	}
	
	/**
	 * Accelerates a Sprite, note that this is relative to current Acceleration. Terminal velocity restrictions are removed.
	 * @param accelerationX acceleration in X direction, pixels per second
	 * @param accelerationY acceleration in Y direction, pixels per second
	 */
	public void accelerate(float accelerationX, float accelerationY) {
		_stopAtTerminalVelocity = false;
		_accelerationX += accelerationX;
		_accelerationY += accelerationY;
		setLastUpdate();
	}
	
	/**
	 * @return current acceleration in X direction, pixels per second
	 */
	public float getAccelerationX() {
		return _accelerationX;
	}
	/**
	 * @return current acceleration in Y direction, pixels per second
	 */
	public float getAccelerationY() {
		return _accelerationY;
	}
	
	/**
	 * @return current velocity in X direction, pixels per second
	 */
	public float getVelocityX() {
		return _velocityX;
	}
	
	/**
	 * @return current velocity in Y direction, pixels per second
	 */
	public float getVelocityY() {
		return _velocityY;
	}
	
	/**
	 * @param velocityX instantly sets the velocity of the Sprite in X direction, pixels per second
	 */
	public void setVelocityX(float velocityX) {
		_velocityX = velocityX;
	}
	
	/**
	 * @param velocityY instantly sets the velocity of the Sprite in Y direction, pixels per second
	 */
	public void setVelocityY(float velocityY) {
		_velocityY = velocityY;
	}
	
	/**
	 * Instantly sets the velocity of te Sprite in X and Y directions, pixels per second
	 * @param velocityX
	 * @param velocityY
	 */
	public void setVelocity(float velocityX, float velocityY) {
		_velocityX = velocityX;
		_velocityY = velocityY;
	}
	
	/**
	 * @return the current terminal velocity cap in X direction
	 */
	public float getTerminalVelocityX() {
		return _terminalVelocityX;
	}
	
	/**
	 * @return the current terminal velocity cap in Y direction
	 */
	public float getTerminalVelocityY() {
		return _terminalVelocityY;
	}
	
	/**
	 * @param stopAtTerminalVelocity TRUE if Sprite should stop at the terminal velocity, FALSE if it should continue accelerating
	 */
	public void setStopAtTerminalVelocity(boolean stopAtTerminalVelocity) {
		_stopAtTerminalVelocity = stopAtTerminalVelocity;
	}
	
	/**
	 * @return TRUE if the Sprite is going to stop when it reaches terminal velocity, FALSE if it will continue indefinately
	 */
	public boolean isStopAtTerminalVelocity() {
		return _stopAtTerminalVelocity;
	}
	
	/**
	 * Sets a terminal velocity at which the Sprite will stop accelerating, this will trigger reachedTerminalVelocityX and reachedTerminalVelocityY in your DynamicsHandler if set
	 * @param terminalVelocityX
	 * @param terminalVelocityY
	 */
	public void setTerminalVelocity(float terminalVelocityX, float terminalVelocityY) {
		_stopAtTerminalVelocity = true;
	}
	
	public void setTerminalVelocityX(float terminalVelocityX) {
		_terminalVelocityX = terminalVelocityX;
	}
	
	public void setTerminalVelocityY(float terminalVelocityY) {
		_terminalVelocityY = terminalVelocityY;
	}
	
	/**
	 * Increases the current velocity by a given value
	 * @param velocityX
	 * @param velocityY
	 */
	public void setVelocityRelative(float velocityX, float velocityY) {
		_velocityX += velocityX;
		_velocityY += velocityY;
	}

	public DynamicObject(Texture texture) {
		super(texture);
		// TODO Auto-generated constructor stub
	}

	/**
	 * Removes the Texture that has been applied to the Sprite
	 */
	public void resetTexture() {
		_texture = null;
	}
	
	
	/**
	 * Updates the texture buffers used by OpenGL, there should be no need to call this
	 */
	public void updateBuffers() {
		_updateTextureBuffer();
	}

	/**
	 * @param red 0 to 255
	 */
	public void setRedInt(int red) {
		_red = (float)red / 255f;
	}

	/**
	 * @param red 0 to 255
	 */
	public void setGreenInt(int green) {
		_green = (float)green / 255f;
	}

	/**
	 * @param red 0 to 255
	 */
	public void setBlueInt(int blue) {
		_blue = (float)blue / 255f;
	}

	/**
	 * @param red 0 to 255
	 */
	public void setAlphaInt(int alpha) {
		_alpha = (float)alpha / 255f;
	}
	
	/**
	 * @return current red value, 0.0 to 1.0
	 */
	public float getRed() {
		return _red;
	}
	
	/**
	 * @return current green value, 0.0 to 1.0
	 */
	public float getGreen() {
		return _green;
	}

	/**
	 * @return current blue value, 0.0 to 1.0
	 */
	public float getBlue() {
		return _blue;
	}

	/**
	 * @return current red value, 0 to 255
	 */
	public int getRedInt() {
		return Math.round(_red * 255);
	}

	/**
	 * @return current green value, 0 to 255
	 */
	public int getGreenInt() {
		return Math.round(_green * 255);
	}

	/**
	 * @return current blue value, 0 to 255
	 */
	public int getBlueInt() {
		return Math.round(_blue * 255);
	}

	/**
	 * @return current alpha value, 0 to 255
	 */
	public int getAlphaInt() {
		return Math.round(_alpha * 255);
	}

	public void drawFrame(GL10 gl) {
		_detectCollisions();
		super.drawFrame(gl);
	}
	/**
	 * Note, this is very basic and does represents only the rectangular Sprite
	 * @param x
	 * @param y
	 * @return TRUE if the Sprite is colliding with the given coordinates
	 */
	public boolean isAt(int x, int y) {
		if(x < getX() || x > getX() + getWidth())
			return false;
		if(y < getY() || y > getY() + getHeight())
			return false;
		return true;
	}
	
	/**
	 * @param spriteModifier a SpriteModifier to remove from the Sprite
	 */
	public void removeModifier(SpriteModifier spriteModifier) {
		for(i = 0; i < MAX_MODIFIERS; i++)
			if(_modifierArr[i].equals(spriteModifier))
				_modifierArr[i] = null;
	}
	
	/**
	 * @param collisionHandler defines a CollisionHandler which the Sprite should trigger if it collides with any of the registered targets
	 */
	public void setCollisionHandler(CollisionHandler collisionHandler) {
		_collisionHandler = collisionHandler;
	}
	
	/**
	 * Removes the CollisionHandler, and no longer checks for collisions
	 */
	public void resetCollisionHandler() {
		_collisionHandler = null;
		for(i = 0; i < MAX_MODIFIERS; i++)
			_modifierArr[i] = null;
	}
	
	/**
	 * @param target adds a target Sprite for the CollisionHandler to check for each frame
	 */
	public void addCollisionSprite(Sprite target) {
		j = -1;
		for(i = 0; i < MAX_COLLIDERS; i++)
			if(_collidersArr[i] == null)
				j = i;
		if(j == -1) {
			Debug.print("TOO MANY SPRITE COLLIDERS");
			return;
		}
		_collidersArr[j] = target;
		_colliderCount++;
	}
	
	/**
	 * @param target removes a target from the Sprite's list
	 */
	public void removeCollisionSprite(Sprite target) {
		for(i = 0; i < MAX_COLLIDERS; i++)
			if(_collidersArr[i] != null)
				if(_collidersArr[i].equals(target))
					_collidersArr[i] = null;
		_colliderCount--;
	}
	
	/**
	 * Removes the AnimationHandler from the Sprite
	 */
	public void resetAnimationHandler() {
		_animationHandler = null;
	}

	private void _detectCollisions() {
		if(_collisionHandler == null || _colliderCount == 0)
			return;

		for(i = 0; i < MAX_COLLIDERS; i++) {
			if(_collidersArr[i] != null)
				if((getX() >= _collidersArr[i].getX() && getX() <= _collidersArr[i].getX() + _collidersArr[i].getWidth()) || (getX() <= _collidersArr[i].getX() && getX() + getWidth() >= _collidersArr[i].getX()))
					if((getY() >= _collidersArr[i].getY() && getY() <= _collidersArr[i].getY() + _collidersArr[i].getHeight()) || (getY() <= _collidersArr[i].getY() && getY() + getHeight() >= _collidersArr[i].getY()))
						_collisionHandler.collision(this, _collidersArr[i]);
		}
	}

	/**
	 * Stops the animation, and leaving the Sprite at its current frame 
	 */
	public void stopAnimation() {
		_animating = false;
	}
	
	/**
	 * @return TRUE if the Sprite is being animated
	 */
	public boolean isAnimating() {
		return _animating;
	}
	
	/**
	 * Resets the sprite to the initial conditions
	 */
	public void reset() {
		reset(true);
	}
	
	/**
	 * Resets the sprite to the initial conditions
	 * @param resetTexture TRUE if the texture is to be reset to the first tile
	 */
	public void reset(boolean resetTexture) {
		super.reset();
		stop();
		stopAnimation();
		if(resetTexture)
			setTileIndex(1);
	}
	
}
